﻿// -----------------------------------------------------------------------------
// <copyright file="Settings.cs" company="Justin Wallace Saunders">
//     Copyright (c) Microsoft Public License. All rights reserved.
// </copyright>
// <author name="Justin Wallace Saunders" email="justinsa@live.ca" />
// -----------------------------------------------------------------------------
namespace Community.Tools.Assembly.Configuration.Loader
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Reflection;
    using System.Web;

    /// <summary>
    /// This class is a singleton for multiple assembly configuration settings.
    /// </summary>
    public class Settings
    {
        /// <summary>
        /// The settings instance.
        /// </summary>
        private static Settings instance = new Settings();

        /// <summary>
        /// Prevents a default instance of the Settings class from being created.
        /// </summary>
        private Settings()
        {
            this.Dictionary = new Dictionary<string, NameValueCollection>();
        }

        /// <summary>
        /// Gets or sets the dictionary source.
        /// </summary>
        private IDictionary<string, NameValueCollection> Dictionary { get; set; }

        /// <summary>
        /// Splits a string value in to sub-tokens on the delimiter.
        /// </summary>
        /// <param name="value">The value to split</param>
        /// <param name="delimiter">The delimeter</param>
        /// <param name="options">The split options</param>
        /// <returns>An enumeration of strings</returns>
        public static IEnumerable<string> SplitSettingValue(string value, string delimiter, StringSplitOptions options)
        {
            if (string.IsNullOrEmpty(delimiter))
            {
                throw new ArgumentNullException("delimiter");
            }

            if (!string.IsNullOrEmpty(value))
            {
                foreach (string token in value.Split(new[] { delimiter }, options))
                {
                    yield return token;
                }
            }
        }

        /// <summary>
        /// Gets the value of a key from a configuration dictionary.
        /// </summary>
        /// <param name="dictionary">The dictionary to use</param>
        /// <param name="key">The key to look-up</param>
        /// <returns>The value related to the key</returns>
        public static string GetSetting(string dictionary, string key)
        {
            if (string.IsNullOrEmpty(dictionary))
            {
                throw new ArgumentNullException("dictionary");
            }

            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }

            if (!instance.Dictionary.ContainsKey(dictionary))
            {
                throw new InvalidOperationException("dictionary does not exist");
            }

            return instance.Dictionary[dictionary][key];
        }

        /// <summary>
        /// Determines whether <paramref name="key"/> has value of <paramref name="value"/>.
        /// </summary>
        /// <param name="dictionary">The dictionary to use</param>
        /// <param name="key">The key to look-up</param>
        /// <param name="value">The value to compare against</param>
        /// <param name="comparisonType">The string comparison type</param>
        /// <returns>True if the values have equality, false otherwise</returns>
        public static bool SettingHasValue(string dictionary, string key, string value, StringComparison comparisonType)
        {
            if (string.IsNullOrEmpty(dictionary))
            {
                throw new ArgumentNullException("dictionary");
            }

            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }

            if (!instance.Dictionary.ContainsKey(dictionary))
            {
                throw new ArgumentException("dictionary is not in collection");
            }

            string sentinel = instance.Dictionary[dictionary][key];
            return !string.IsNullOrEmpty(sentinel) && string.Equals(sentinel, value, comparisonType);
        }

        /// <summary>
        /// Loads the AppSettings for the specified binary path to the
        /// dictionary under <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The dictionary key</param>
        /// <param name="assemblyType">A type in the calling assembly</param>
        /// <returns>True if successful, false otherwise</returns>
        /// <remarks>
        /// In order to allow the configuration file to be loaded for the
        /// assembly, multiple destinations have to be checked for the
        /// existence of the config file. Depending on the runtime environment
        /// the config file will be located in different locations
        /// relative to the runtime resolution location.
        /// IIS / ASP.NET --> System.AppDomain.CurrentDomain.CodeBase
        /// Application --> Assembly.Location
        /// </remarks>
        public static bool LoadDictionary(string key, Type assemblyType)
        {
            // Look in the binary directory
            string path = Assembly.GetAssembly(assemblyType).Location;
            if (LoadDictionary(key, path))
            {
                return true;
            }

            // Look in the bin directory
            if (HttpContext.Current != null || AppDomain.CurrentDomain != null)
            {
                path = Assembly.GetAssembly(assemblyType).CodeBase.Replace(@"file:///", string.Empty);
                if (LoadDictionary(key, path))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Loads the AppSettings for the specified binary path to the
        /// dictionary under <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The dictionary key</param>
        /// <param name="exePath">The path to the binary</param>
        /// <returns>True if successful, false otherwise</returns>
        public static bool LoadDictionary(string key, string exePath)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key");
            }

            if (string.IsNullOrEmpty(exePath))
            {
                throw new ArgumentNullException("exePath");
            }

            if (instance.Dictionary.ContainsKey(key))
            {
                throw new ArgumentException("key is already in dictionary");
            }

            Configuration configuration = null;
            try
            {
                configuration = ConfigurationManager.OpenExeConfiguration(exePath);
            }
            catch (ConfigurationErrorsException)
            {
                configuration = null;
            }

            if (configuration == null || !configuration.HasFile)
            {
                return false;
            }

            NameValueCollection collection = new NameValueCollection(configuration.AppSettings.Settings.Count);
            foreach (KeyValueConfigurationElement element in configuration.AppSettings.Settings)
            {
                collection.Add(element.Key, element.Value);
            }

            instance.Dictionary.Add(key, collection);
            return true;
        }
    }
}
